BPF-eBPF 学习总览:从概念、机制到工具链选择

世界上只有一种真正的英雄主义,就是在认清生活的真相后依然热爱生活。——罗曼·罗兰

写在前面


  • 这篇文章的目标不是只解释 BPF/eBPF 的定义,而是给出一条由浅及深、能真正走通的学习路线
  • 内容会从概念认知运行机制学习顺序工具链选择一直讲到 BCClibbpfebpf-go 的入门方式
  • 如果你是第一次接触 eBPF,建议先把为什么它重要它是怎么运行起来的搞明白,再开始写程序
  • 理解不足小伙伴帮忙指正 :)

世界上只有一种真正的英雄主义,就是在认清生活的真相后依然热爱生活。——罗曼·罗兰

持续分享技术干货,感兴趣小伙伴可以关注下 ^_^


系列导航

这一组文章统一放在 source/_posts/rhca/RH442_BPF/BPF/eBPF学习路线/ 目录下,阅读顺序建议如下:

  1. BPF-eBPF 学习总览:从概念、机制到工具链选择
  2. BPF-eBPF 实战入门:环境准备、最小实验与排错思路
  3. BPF-eBPF 开发路线一:BCC 入门、工具使用与自定义脚本
  4. BPF-eBPF 开发路线二:libbpf、CO-RE 与 libbpf-bootstrap 实战
  5. BPF-eBPF 开发路线三:ebpf-go、bpf2go 与 Go 工程集成

这一篇侧重认知框架学习路线,后面几篇侧重环境、命令、示例和代码组织

先给结论:BPF 应该怎么学

如果你想少走弯路,我建议按下面这个顺序学习:

  1. 先理解 BPF/eBPF 的核心概念,至少搞懂:hookprogram typemaphelperverifierJIT
  2. 再理解 eBPF 为什么适合做 networkingobservabilitysecurity
  3. 然后用现成工具先建立直觉,比如看别人已经写好的 BCC 工具在观测什么
  4. 之后再开始写自己的第一个程序,优先做最小实验:tracepointkprobeuprobe
  5. 等你能写简单程序了,再去系统掌握 CO-REBTFbpftoolringbuf/perf buffer、对象生命周期
  6. 最后根据你的主语言和目标场景,在 BCClibbpfebpf-go 之间做取舍

一句话概括:

  • 快速上手、快速验证想法,先看 BCC
  • 贴近内核、做主流生产级开发,重点学 libbpf
  • 用户态用 Go 集成、做可分发工具,重点学 ebpf-go

什么是 BPF,什么是 eBPF

BPF 最早是 Berkeley Packet Filter,最初和网络包过滤强相关。

后来 Linux 内核把它演进成了一个更通用的执行引擎,也就是今天大家常说的 eBPF。在官方文档和社区里,BPFeBPF 往往会混用;严格说:

  • cBPF 指经典 BPF
  • eBPF 指扩展后的 BPF
  • 现在内核和很多文档里经常直接写 BPF

根据 ebpf.io 的介绍,eBPF 本质上是在 Linux 内核中运行的一套沙箱化程序机制,可以在不修改内核源码、不加载传统内核模块的前提下,安全地扩展内核能力。

它今天最重要的三个应用方向是:

  • 网络
  • 可观测性
  • 安全

这也是为什么 eBPF 这几年会这么火。它不是单纯的“抓包工具升级版”,而是一种可以把观测逻辑过滤逻辑安全策略数据采样逻辑放进内核关键路径执行的能力。

学 eBPF 之前,先建立一张脑图

很多人学 eBPF 卡住,不是因为语法难,而是因为脑子里没有完整的运行模型。你至少要先建立下面这张“心智模型”。

1. 程序不是主动运行的,而是挂在 hook 上

eBPF 程序是事件驱动的,不是像普通用户态程序那样自己主动跑起来。

它会挂载在某个 hook point 上,等事件发生时再执行,例如:

  • kprobe/kretprobe:挂在内核函数入口/返回
  • tracepoint:挂在内核预定义跟踪点
  • uprobe/uretprobe:挂在用户态函数入口/返回
  • XDP:挂在网络收包非常早的阶段
  • tc:挂在流量控制路径
  • perf_event:做采样类分析
  • LSM:做安全控制

你可以把 eBPF 理解成:

  • 程序本体
  • 挂载位置
  • 和用户态交换数据的方式

这三件事缺一不可。

2. eBPF 程序不能乱来,它要过 verifier

eBPF 最核心的安全机制之一就是 verifier

根据 ebpf.io 的介绍,程序在加载到内核前会经过校验,验证器会确保它:

  • 不会非法访问内存
  • 不会无限循环不退出
  • 不会使用未初始化变量
  • 复杂度在内核允许的范围内

所以你学习 eBPF,不能只想着“怎么写代码”,还要学会“怎么写出 verifier 能接受的代码”。

3. eBPF 程序很小,复杂状态通常放在 map 里

eBPF 程序本身执行时间短、约束强,不适合在里面维护复杂业务状态,因此大量状态和数据交换依赖 BPF map

docs.ebpf.io 将 map 类型分成了很多类,常见的有:

  • HASH
  • ARRAY
  • PERCPU_HASH
  • PERCPU_ARRAY
  • LPM_TRIE
  • RINGBUF
  • PERF_EVENT_ARRAY
  • PROG_ARRAY

学 eBPF 时,map 绝对不是附属知识,而是主知识。很多程序难点根本不在 probe,而在:

  • 数据怎么聚合
  • 如何把内核态数据送到用户态
  • 怎么避免竞争
  • 怎么控制开销

4. eBPF 不是直接调用任意内核函数,而是通过 helper/kfunc 等受控能力

eBPF 程序不能像内核模块那样随便调用内核内部函数,而是依靠内核暴露的能力接口,例如:

  • helper functions
  • 一些场景下的 kfunc

这也是它能兼顾灵活性和安全性的原因之一。

5. 最终是“内核态采集 + 用户态控制/展示”

绝大多数 eBPF 应用都不是纯内核态,也不是纯用户态,而是“两段式结构”:

  • 内核态 eBPF 程序负责挂点、采集、过滤、聚合
  • 用户态程序负责加载、附加、读取 map/ringbuf、展示或上报

你后面无论使用 BCClibbpf 还是 ebpf-go,本质上都在做这件事,只是工程组织方式不同。

初学者到底需要哪些前置知识

如果下面这些你完全陌生,建议先补一下,不然学 eBPF 会非常痛苦:

  • Linux 基础:进程、线程、文件、权限、系统调用
  • 内核基础:用户态/内核态、调度、中断、网络栈、VFS 的基本概念
  • C 语言基础:结构体、指针、内存布局
  • 调试与观测straceperfsstcpdumpbpftool
  • 编译基础:至少知道 clang/LLVM 在这里扮演什么角色

如果你的目标是用 ebpf-go,那还要有:

  • Go 基础
  • file descriptor、资源生命周期有基本认知

一条更稳妥的学习路线

下面这条路线适合大多数工程师。

第一阶段:只看概念,不急着写代码

这一阶段只做一件事:先把“eBPF 到底是什么”搞明白。

建议阅读顺序:

  1. ebpf.io/what-is-ebpf/
  2. docs.cilium.io 里的 BPF and XDP Reference Guide
  3. docs.ebpf.io

你在这一阶段至少应该回答这几个问题:

  • 为什么 eBPF 可以在内核里运行,但又比传统内核模块更安全
  • 什么叫 hook,常见 hook 有哪些
  • 什么是 program type
  • 什么是 map,为什么 map 很重要
  • verifier 在检查什么
  • JIT、helper、tail call 分别解决什么问题

如果这些问题答不清楚,不建议直接往“写程序”上冲。

第二阶段:先学会“看”,再学会“写”

不要一上来就手写复杂 eBPF 程序,先学会用别人写好的工具观察系统。

这是因为 eBPF 真正的门槛不只是语法,而是:

  • 你想观测什么
  • 这个点挂在哪里
  • 数据应该如何汇总
  • 输出长什么样才有分析价值

所以这一阶段建议你:

  • 先跑一些 BCC 工具
  • 看它们分别在采什么数据
  • 观察它们挂了哪些 probe
  • 理解为什么有些工具输出直方图,有些输出事件流

比如 bcc 官方仓库里就提供了大量现成工具,覆盖:

  • tracing
  • memory and process
  • performance and time
  • CPU and scheduler
  • network and sockets

这一步非常重要,因为它会帮你建立“eBPF 能解决什么问题”的直觉。

第三阶段:写最小可运行程序

这个阶段不要上来就做复杂项目,而是做最小闭环

  1. 写一个最简单的 tracepoint 程序
  2. 把数据打印到 trace_pipe 或送到用户态
  3. 再写一个简单的 kprobe
  4. 再尝试一个 uprobe
  5. 最后尝试一个基于 ringbuf/perf buffer 的事件传递

学习顺序建议如下:

  • tracepoint:最稳,符号稳定,适合入门
  • kprobe:更灵活,但更依赖内核函数细节
  • uprobe:开始接触用户态应用跟踪
  • XDP/tc:进入网络方向
  • LSM:进入安全方向

第四阶段:系统掌握工程化能力

到了这一步,你已经不是“知道 eBPF 是什么”,而是开始“真正做 eBPF 应用”了。

这个阶段要重点掌握:

  • BTF
  • CO-RE
  • bpftool
  • vmlinux.h
  • pinning
  • ring bufferperf event array
  • 对象生命周期
  • 不同内核版本兼容性

这部分一旦不懂,程序就很容易变成:

  • 只能在你自己机器上跑
  • 一换内核就挂
  • 一上线就因为权限或资源限制失败

三种主流开发方式怎么选

你给出的三种方式,基本就是今天最常见的 eBPF 开发路线。

1. BCC:最适合入门、验证和快速做工具

BCC 是一个 eBPF 工具集和开发框架。根据其官方 README,它提供了大量现成工具,并且支持用 C 编写内核态逻辑,配合 Python/Lua 等前端进行用户态控制。

它的优点:

  • 上手快
  • 适合教学和原型验证
  • 自带很多现成工具,便于学习思路
  • 做临时排障、性能分析很高效

它的不足:

  • 运行时依赖相对多
  • 更依赖目标机环境
  • 对生产级可移植性和现代工程化支持,通常不如 libbpf + CO-RE

什么时候学 BCC:

  • 你刚开始接触 eBPF
  • 你想快速理解各种探针和观测方法
  • 你想先写几个实验工具建立信心

一句话评价:BCC 非常适合“学会 eBPF 能干什么”

2. libbpf:最值得深入的主流 C 开发路线

libbpf 是今天 Linux eBPF 生态里非常核心的用户态库。它更贴近内核原生能力,也是很多现代 eBPF 项目的基础设施。

libbpf-bootstrap 官方仓库的定位就很明确:它是一个用于 BPF 应用开发的脚手架,提供了 minimalbootstrapkprobeuprobeusdtfentryxdptc 等示例。

尤其是 bootstrap 这个例子,很适合作为真正开始写应用的起点,因为它把很多“必须做但很枯燥”的事先帮你搭好了,例如:

  • 加载对象
  • 处理命令行参数
  • 使用 ring buffer 把数据送到用户态
  • 使用 CO-REvmlinux.h
  • 优雅退出

libbpf 的优点:

  • 更贴近内核原生生态
  • CO-RE 支持成熟
  • 更适合做长期维护、生产部署的程序
  • 学透之后,对 eBPF 机制理解会更深

它的不足:

  • 学习曲线更陡
  • ELF section、对象装载、BTF、attach 方式等概念要求更高
  • 对 C 语言和内核数据结构理解要求更高

什么时候优先学 libbpf:

  • 你想真正掌握 eBPF
  • 你要写生产级程序
  • 你希望理解内核原生 BPF 应用是怎么组织的

一句话评价:libbpf 非常适合“把 eBPF 学扎实”

3. ebpf-go:适合 Go 工程体系中的 eBPF 集成

ebpf-go 是 Go 生态中非常重要的 eBPF 库。其官方首页明确说明:它是一个用于操作 eBPF 的 Go 库,不依赖 Clibbpf 或其它 Go 库之外的额外运行时依赖,这使它很适合做自包含、可移植的工具。

这条路线很适合:

  • 你的用户态控制面本来就是 Go
  • 你要把 eBPF 集成到现有 Go 服务或 CLI 中
  • 你希望产物分发更方便

ebpf-go 文档里还有两个对初学者很重要的点:

  • Object Lifecycle:它强调 eBPF 对象本质上围绕文件描述符运作,而 Go 的垃圾回收会影响对象生命周期
  • Portable eBPF:它建议用稳定的 LLVM 工具链生成 .o.go 文件,并结合 CO-RE/BTF 处理跨内核兼容问题

这意味着用 ebpf-go 时,你不仅要会 Go,还要理解:

  • 文件描述符何时被关闭
  • pinning 何时需要
  • ProgramArray/tail call 为什么会踩生命周期坑
  • bpf2go 生成物是怎么参与构建的

什么时候优先学 ebpf-go:

  • 你主力语言是 Go
  • 你要开发命令行工具、Agent、Exporter、探针守护进程
  • 你不想把用户态控制面写成 C

一句话评价:ebpf-go 非常适合“把 eBPF 融入 Go 工程”

三种方式的选择建议

如果你现在还没有明确方向,可以直接参考这张表:

目标 更建议的路线
先建立直觉,快速做实验 BCC
深入理解 eBPF 核心机制,做主流生产开发 libbpf
用户态以 Go 为主,想做工程化交付 ebpf-go
临时排障、性能分析、验证想法 BCC
做长期维护的底层工具或 Agent libbpf / ebpf-go

如果你问我最稳妥的学习顺序,我会建议:

概念 -> BCC 建立直觉 -> libbpf 打基础 -> ebpf-go 做工程集成

一个可执行的 6 周学习计划

如果你希望不是“看完文章很激动”,而是真的开始学,可以按这个节奏走。

第 1 周:概念入门

目标:

  • 搞懂 BPF/eBPF 是什么
  • 知道常见 hook 和 program type
  • 知道 verifier、JIT、map、helper 是什么

产出:

  • 能用自己的话解释 eBPF 的运行模型

第 2 周:观察现成工具

目标:

  • 跑一些 BCC 工具
  • 看 CPU、内存、进程、网络四类场景
  • 形成“一个问题对应一个挂点和一类数据”的直觉

产出:

  • 至少记录 10 个你看过的工具和它们各自解决的问题

第 3 周:第一个最小程序

目标:

  • 跑通一个 tracepoint
  • 跑通一个 kprobe
  • 理解内核态和用户态怎么通信

产出:

  • 你的第一个最小 eBPF Demo

第 4 周:开始 libbpf

目标:

  • libbpf-bootstrap
  • minimal
  • bootstrap
  • 理解 vmlinux.hBTFCO-RE

产出:

  • 基于 bootstrap 改一个自己的小程序

第 5 周:开始 ebpf-go

目标:

  • 跑通 ebpf-go 的 getting started
  • bpf2go 生成对象
  • 理解 Go 侧如何加载、attach、读取数据

产出:

  • 一个 Go 版本的小型观测工具

第 6 周:选一个方向深入

你可以三选一:

  • 可观测性:进程、内存、CPU、IO、网络延迟
  • 网络:XDP、tc、socket 相关路径
  • 安全:LSM、syscall、进程行为约束

产出:

  • 一个可以真正解决问题的小工具,而不是 demo

非常适合新手的练手题

如果你不知道从什么开始写,下面这些题目适合作为练习:

  1. 统计某个进程的 exec/exit 事件
  2. 跟踪指定进程的文件打开行为
  3. 统计某个 syscall 的耗时分布
  4. 观察 TCP 连接建立时延
  5. 跟踪用户态某个函数的调用次数和耗时
  6. 用 ring buffer 把事件推给用户态程序

这些练手题的价值在于它们分别覆盖了:

  • 事件跟踪
  • 数据聚合
  • 直方图统计
  • 内核态到用户态通信
  • probe 选择
  • attach 生命周期

学习过程中最容易踩的坑

1. 只记语法,不理解挂点

很多人会背 SEC("kprobe/...")BPF_HASH 之类写法,但并不知道为什么要挂这个点。这样一旦换问题场景,立刻不会写。

2. 把 eBPF 当成普通 C 去写

eBPF 代码受 verifier 和执行模型强约束,很多普通 C 的写法并不成立。你必须一直带着“这段代码能不能通过 verifier”的意识写。

3. 不理解 map 的角色

很多程序写不出来,本质上不是 probe 不会挂,而是数据结构没设计清楚。

4. 不重视兼容性

如果你以后要把程序部署到不同机器上,就必须理解:

  • BTF
  • CO-RE
  • 不同内核版本差异
  • 不同发行版内核布局差异

5. 不理解对象生命周期

尤其是 ebpf-go 场景下,文件描述符、GC、pinning、link 生命周期如果没搞清楚,非常容易出现“程序看起来加载成功了,但过一会儿就失效”的问题。

我建议的资料阅读顺序

下面按“从入门到开发”的顺序整理一下你给的参考资料。

第一层:概念入门

  1. https://ebpf.io/what-is-ebpf/
  2. https://docs.cilium.io/en/stable/reference-guides/bpf/index.html
  3. https://docs.ebpf.io/

适合解决的问题:

  • eBPF 到底是什么
  • 运行模型是什么
  • 常见 program type、map type、helper 有哪些
  • 什么是更系统的技术参考资料

第二层:BCC 入门

  1. https://github.com/iovisor/bcc

建议看法:

  • 先不要急着通读源码
  • 先看 README 和现成工具清单
  • 挑你最关心的方向去读工具实现

适合人群:

  • 想快速开始
  • 想先建立“观测问题 -> 挂点 -> 数据输出”的直觉

第三层:libbpf 入门与深入

  1. https://github.com/libbpf/libbpf-bootstrap
  2. https://libbpf.readthedocs.io/en/latest/

建议顺序:

  • 先从 libbpf-bootstrap 的示例开始
  • 再去读 libbpf 文档
  • 最后再结合需要直接看 libbpf 源码

适合人群:

  • 想系统掌握 eBPF C 开发
  • 想进入生产级工具开发

第四层:Go 路线

  1. https://ebpf-go.dev/

建议重点:

  • Getting Started
  • Portable eBPF
  • Object Lifecycle

适合人群:

  • Go 工程师
  • 需要把 eBPF 集成到现有 Go 系统中的同学

一条更现实的工具链建议

很多人纠结“我到底先学 BCC、libbpf 还是 ebpf-go”,其实更现实的答案是:

  • BCC 学观察问题的方法
  • libbpf 学 eBPF 的主干知识
  • ebpf-go 完成工程集成

也就是说,三者不是绝对对立关系,而是很可能在不同阶段各有价值。

如果你是:

  • 运维/性能分析方向:优先 BCC -> libbpf
  • 底层系统开发方向:优先 libbpf
  • Go 平台/Agent/可观测性平台方向:优先 libbpf 基础 -> ebpf-go

参考资料与说明

本文整理时主要参考了以下资料:

结合这些资料,可以得到一条比较稳妥的判断:

  • ebpf.io 适合建立整体认知
  • docs.ebpf.io 适合作为长期查询手册
  • BCC 适合入门和建立问题直觉
  • libbpf 是最值得系统深入的主路线
  • ebpf-go 非常适合 Go 工程中的实际集成

最后总结

如果你刚开始学 BPF,不要把目标定成“我今天就写出一个很强的 eBPF 工具”,而应该先做到:

  • 知道它为什么存在
  • 知道它运行在什么位置
  • 知道它怎么安全运行
  • 知道数据怎么从内核态流到用户态
  • 知道三种主流开发方式分别适合什么场景

当这些问题想清楚之后,后面的学习就会顺很多。

真正的学习顺序不是:

背 API -> 抄 demo -> 卡在 verifier

而应该是:

建立模型 -> 学会观察 -> 做最小实验 -> 进入工程化

这样学,eBPF 才会越学越清楚,而不是越学越乱。

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)



© 2018-至今 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

发布于

2026-04-14

更新于

2026-04-15

许可协议

评论
加载中,最新评论有1分钟缓存...
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×